Skip to content

S01-17 JavaSE-泛型

[TOC]

泛型的理解和好处

传统方法的问题

需求:在 ArrayList 中添加 3 个 Dog 对象,输出 name 和 age。 传统方法实现(无泛型):

java
package com.hspedu.generic;
import java.util.ArrayList;

@SuppressWarnings({"all"})
public class Generic01 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Dog("旺财", 10));
        arrayList.add(new Dog("发财", 1));
        arrayList.add(new Dog("小黄", 5));
        // 不小心添加了 Cat 对象(编译无报错,运行时异常)
        arrayList.add(new Cat("招财猫", 8));

        // 遍历:需要向下转型,效率低且不安全
        for (Object o : arrayList) {
            Dog dog = (Dog) o;// 运行时抛出 ClassCastException
            System.out.println(dog.getName() + "-" + dog.getAge());
        }
    }
}

class Dog {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

class Cat {
    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

传统方法的问题分析

  1. 不能约束集合中元素的类型(添加 Cat 编译无报错)
  2. 遍历需要类型转换,数据量大时效率低
  3. 运行时可能抛出 ClassCastException

泛型快速体验(Generic02.java)

java
package com.hspedu.generic.improve;
import java.util.ArrayList;

@SuppressWarnings({"all"})
public class Generic02 {
    public static void main(String[] args) {
        // 泛型约束:只能添加 Dog 类型
        ArrayList<Dog> arrayList = new ArrayList<Dog>();
        arrayList.add(new Dog("旺财", 10));
        arrayList.add(new Dog("发财", 1));
        arrayList.add(new Dog("小黄", 5));
        // arrayList.add(new Cat("招财猫", 8));// 编译报错,类型不匹配

        // 遍历:无需类型转换,直接获取 Dog 类型
        System.out.println("===使用泛型===");
        for (Dog dog : arrayList) {
            System.out.println(dog.getName() + "-" + dog.getAge());
        }
    }
}

class Dog {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

泛型的好处

  1. 编译时检查类型,提高安全性(避免添加错误类型)
  2. 减少类型转换次数,提高效率:
    • 无泛型:Dog → Object → Dog(添加时装箱,取出时拆箱)
    • 有泛型:Dog → Dog(无需转换)
  3. 消除编译警告

泛型介绍

  1. 泛型又称参数化类型,JDK 5.0 新特性,解决数据类型安全性问题
  2. 类声明或实例化时指定具体类型
  3. 编译时无警告则运行时无 ClassCastException
  4. 泛型可用于类的属性、方法参数、返回值类型

泛型示例(Generic03.java)

java
package com.hspedu.generic;

@SuppressWarnings({"all"})
public class Generic03 {
    public static void main(String[] args) {
        // 实例化时指定泛型类型为 String
        Person<String> person = new Person<String>("韩顺平教育");
        person.show();// 输出:class java.lang.String

        // 实例化时指定泛型类型为 Integer
        Person<Integer> person2 = new Person<Integer>(100);
        person2.show();// 输出:class java.lang.Integer
    }
}

// 泛型类:T 为类型参数
class Person<T> {
    T s;// 属性使用泛型

    // 构造器使用泛型
    public Person(T s) {
        this.s = s;
    }

    // 方法返回值使用泛型
    public T f() {
        return s;
    }

    // 显示 s 的运行类型
    public void show() {
        System.out.println(s.getClass());
    }
}

泛型的语法

泛型的声明

java
// 接口泛型
interface 接口名<T, R...> {}
// 类泛型
class 类名<K, V...> {}
  • T、K、V 是类型占位符,任意字母均可,常用 T(Type)、K(Key)、V(Value)
  • 可声明多个泛型参数(如 <T, R, M>

泛型的实例化

java
// 完整写法
List<String> list1 = new ArrayList<String>();
// 简化写法(Java 7+ 类型推断)
List<String> list2 = new ArrayList<>();
// 迭代器泛型
Iterator<Customer> iterator = customers.iterator();

泛型使用举例(GenericExercise.java)

需求

  1. 创建 3 个 Student 对象
  2. 放入 HashSet(泛型)
  3. 放入 HashMap(Key:姓名,Value:Student 对象)
  4. 两种方式遍历
java
package com.hspedu.generic;
import java.util.*;

@SuppressWarnings({"all"})
public class GenericExercise {
    public static void main(String[] args) {
        // 1. HashSet 存储 Student
        HashSet<Student> students = new HashSet<Student>();
        students.add(new Student("jack", 18));
        students.add(new Student("tom", 28));
        students.add(new Student("mary", 19));
        // 遍历 HashSet(增强 for)
        System.out.println("===HashSet 遍历===");
        for (Student student : students) {
            System.out.println(student);
        }

        // 2. HashMap 存储(Key:String,Value:Student)
        HashMap<String, Student> hm = new HashMap<String, Student>();
        hm.put("milan", new Student("milan", 38));
        hm.put("smith", new Student("smith", 48));
        hm.put("hsp", new Student("hsp", 28));

        // 遍历 HashMap 方式1:EntrySet + 迭代器
        System.out.println("===HashMap 迭代器遍历===");
        Set<Map.Entry<String, Student>> entries = hm.entrySet();
        Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Student> next = iterator.next();
            System.out.println(next.getKey() + "-" + next.getValue());
        }

        // 遍历 HashMap 方式2:KeySet + 增强 for
        System.out.println("===HashMap KeySet 遍历===");
        Set<String> keySet = hm.keySet();
        for (String key : keySet) {
            System.out.println(key + "-" + hm.get(key));
        }
    }
}

class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

泛型使用的注意事项和细节

  1. 泛型参数只能是引用类型,不能是基本类型:
    java
    List<Integer> list = new ArrayList<>();// OK
    // List<int> list2 = new ArrayList<>();// 错误
  2. 指定泛型类型后,可传入该类型或其子类:
    java
    Pig<A> pig = new Pig<>(new B());// B 是 A 的子类,OK
  3. 泛型简化写法:
    java
    ArrayList<Integer> list = new ArrayList<>();// 等价于 new ArrayList<Integer>()
  4. 未指定泛型时,默认是 Object:
    java
    ArrayList list = new ArrayList<>();// 等价于 ArrayList<Object> list = new ArrayList<>()

泛型课堂练习(GenericExercise02.java)

需求

  1. 定义 Employee 类(name, sal, birthday),birthday 为 MyDate 类(year, month, day)
  2. 重写 toString,实现 getter/setter
  3. 创建 3 个 Employee 对象,放入 ArrayList(泛型)
  4. 排序:先按 name 升序,name 相同则按生日升序(定制排序)
java
package com.hspedu.generic;
import java.util.ArrayList;
import java.util.Comparator;

@SuppressWarnings({"all"})
public class GenericExercise02 {
    public static void main(String[] args) {
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("tom", 20000, new MyDate(1980, 12, 11)));
        employees.add(new Employee("jack", 12000, new MyDate(2001, 12, 12)));
        employees.add(new Employee("tom", 50000, new MyDate(1980, 12, 10)));

        System.out.println("排序前:" + employees);

        // 定制排序
        employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee emp1, Employee emp2) {
                // 验证类型
                if (!(emp1 instanceof Employee && emp2 instanceof Employee)) {
                    System.out.println("类型不正确");
                    return 0;
                }
                // 按 name 比较
                int nameCompare = emp1.getName().compareTo(emp2.getName());
                if (nameCompare != 0) {
                    return nameCompare;
                }
                // name 相同,按生日比较(MyDate 需实现 compareTo)
                return emp1.getBirthday().compareTo(emp2.getBirthday());
            }
        });

        System.out.println("排序后:" + employees);
    }
}

// Employee 类
class Employee {
    private String name;
    private double sal;
    private MyDate birthday;

    public Employee(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getSal() { return sal; }
    public void setSal(double sal) { this.sal = sal; }
    public MyDate getBirthday() { return birthday; }
    public void setBirthday(MyDate birthday) { this.birthday = birthday; }

    @Override
    public String toString() {
        return "Employee{" + "name='" + name + '\'' + ", sal=" + sal + ", birthday=" + birthday + '}';
    }
}

// MyDate 类(实现 Comparable 接口)
class MyDate implements Comparable<MyDate> {
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    // getter/setter
    public int getYear() { return year; }
    public void setYear(int year) { this.year = year; }
    public int getMonth() { return month; }
    public void setMonth(int month) { this.month = month; }
    public int getDay() { return day; }
    public void setDay(int day) { this.day = day; }

    @Override
    public String toString() {
        return year + "-" + month + "-" + day;
    }

    // 按年月日比较
    @Override
    public int compareTo(MyDate o) {
        int yearCompare = Integer.compare(this.year, o.year);
        if (yearCompare != 0) return yearCompare;
        int monthCompare = Integer.compare(this.month, o.month);
        if (monthCompare != 0) return monthCompare;
        return Integer.compare(this.day, o.day);
    }
}

自定义泛型

自定义泛型类

基本语法

java
class 类名<T, R...> {
    // 成员属性、方法可使用泛型
}

注意细节

  1. 普通成员(属性、方法)可使用泛型
  2. 泛型数组不能初始化(无法确定类型,无法分配内存)
  3. 静态方法/静态属性不能使用类的泛型(静态与类相关,类加载时泛型未确定)
  4. 泛型类型在创建对象时确定
  5. 未指定泛型时,默认是 Object

应用案例(CustomGeneric_.java)

java
package com.hspedu.customgeneric;
import java.util.Arrays;

@SuppressWarnings({"all"})
public class CustomGeneric_ {
    public static void main(String[] args) {
        // 创建对象时指定泛型:T=Double, R=String, M=Integer
        Tiger<Double, String, Integer> tiger = new Tiger<>("john");
        tiger.setT(10.9);// OK
        // tiger.setT("abc");// 错误,类型不匹配
        System.out.println(tiger);

        // 未指定泛型,默认 Object
        Tiger tiger2 = new Tiger("john~~");
        tiger2.setT("yy");// OK(T=Object)
        System.out.println("tiger2=" + tiger2);
    }
}

class Tiger<T, R, M> {
    String name;
    R r;// 属性使用泛型
    M m;// 属性使用泛型
    T t;// 属性使用泛型
    T[] ts;// 泛型数组(不能初始化)

    // 构造器
    public Tiger(String name) {
        this.name = name;
    }

    public Tiger(String name, R r, M m, T t) {
        this.name = name;
        this.r = r;
        this.m = m;
        this.t = t;
    }

    // 方法使用泛型
    public R getR() { return r; }
    public void setR(R r) { this.r = r; }
    public M getM() { return m; }
    public void setM(M m) { this.m = m; }
    public T getT() { return t; }
    public void setT(T t) { this.t = t; }

    @Override
    public String toString() {
        return "Tiger{" + "name='" + name + '\'' + ", r=" + r + ", m=" + m + ", t=" + t + ", ts=" + Arrays.toString(ts) + '}';
    }
}

自定义泛型接口

基本语法

java
interface 接口名<T, R...> {
    // 普通方法可使用泛型
    R method(U u);
}

注意细节

  1. 接口中静态成员不能使用泛型
  2. 泛型类型在继承接口或实现接口时确定
  3. 未指定泛型时,默认是 Object

应用案例(CustomInterfaceGeneric.java)

java
package com.hspedu.customgeneric;

@SuppressWarnings({"all"})
public class CustomInterfaceGeneric {
    public static void main(String[] args) {
        // 测试实现类
        AA aa = new AA();
        System.out.println(aa.get("hello"));// null
        BB bb = new BB();
        System.out.println(bb.get(100));// null
    }
}

// 泛型接口
interface IUsb<U, R> {
    int n = 10;// 静态常量(不能使用泛型)
    R get(U u);
    void hi(R r);
    void run(R r1, R r2, U u1, U u2);
    // JDK 8+ 默认方法(可使用泛型)
    default R method(U u) {
        return null;
    }
}

// 继承接口时指定泛型类型
interface IA extends IUsb<String, Double> {}

// 实现 IA 接口(已指定泛型)
class AA implements IA {
    @Override
    public Double get(String s) {
        return null;
    }

    @Override
    public void hi(Double aDouble) {}

    @Override
    public void run(Double r1, Double r2, String u1, String u2) {}
}

// 实现接口时指定泛型类型
class BB implements IUsb<Integer, Float> {
    @Override
    public Float get(Integer integer) {
        return null;
    }

    @Override
    public void hi(Float aFloat) {}

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {}
}

// 未指定泛型,默认 Object
class CC implements IUsb {
    @Override
    public Object get(Object o) {
        return null;
    }

    @Override
    public void hi(Object o) {}

    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {}
}

自定义泛型方法

基本语法

java
修饰符 <T, R...> 返回类型 方法名(参数列表) {
    // 方法体
}

注意细节

  1. 泛型方法的泛型参数声明在修饰符和返回类型之间(<T, R...>
  2. 泛型方法可定义在普通类或泛型类中
  3. 泛型方法的类型在调用时确定(编译器根据参数推断)
  4. 与类的泛型独立(方法自己的泛型与类泛型无关)

应用案例(CustomMethodGeneric.java)

java
package com.hspedu.customgeneric;
import java.util.ArrayList;

@SuppressWarnings({"all"})
public class CustomMethodGeneric {
    public static void main(String[] args) {
        Car car = new Car();
        // 调用泛型方法,编译器推断 T=String, R=Integer
        car.fly("宝马", 100);
        // 调用泛型方法,编译器推断 T=Integer, R=Double
        car.fly(300, 100.1);

        // 泛型类中的泛型方法
        Fish<String, ArrayList> fish = new Fish<>();
        // 调用泛型方法,编译器推断 U=ArrayList, M=Float
        fish.hello(new ArrayList(), 11.3f);
    }
}

// 普通类中的泛型方法
class Car {
    public <T, R> void fly(T t, R r) {
        System.out.println("t 的类型:" + t.getClass().getSimpleName());
        System.out.println("r 的类型:" + r.getClass().getSimpleName());
    }
}

// 泛型类中的泛型方法
class Fish<T, R> {
    // 普通方法(使用类的泛型)
    public void hi(T t) {
        System.out.println("t 的类型:" + t.getClass().getSimpleName());
    }

    // 泛型方法(自己的泛型 U, M)
    public <U, M> void hello(R r, M m) {
        System.out.println("r 的类型:" + r.getClass().getSimpleName());
        System.out.println("m 的类型:" + m.getClass().getSimpleName());
    }
}

泛型的继承和通配符

核心规则

  1. 泛型不具备继承性:
    java
    // List<Object> list = new ArrayList<String>();// 错误
  2. 通配符 <?>:支持任意泛型类型
    java
    List<?> list = new ArrayList<String>();// OK
    list = new ArrayList<Integer>();// OK
  3. 上限通配符 <? extends A>:支持 A 类及子类
    java
    List<? extends Animal> list = new ArrayList<Dog>();// OK(Dog 是 Animal 子类)
    // list = new ArrayList<String>();// 错误(String 不是 Animal 子类)
  4. 下限通配符 <? super A>:支持 A 类及父类
    java
    List<? super Dog> list = new ArrayList<Animal>();// OK(Animal 是 Dog 父类)
    // list = new ArrayList<String>();// 错误(String 不是 Dog 父类)

应用场景

  • 上限通配符:读取数据(如获取集合中元素,无需修改)
  • 下限通配符:写入数据(如向集合中添加元素,无需读取)
  • 无界通配符:既需要读取又需要写入(但限制较多))

第15章 泛型与JUnit

泛型的继承和通配符

核心代码示例

java
import java.util.ArrayList;
import java.util.List;

/**
 * 泛型的继承和通配符
 * @author 韩顺平
 * @version 1.0
 */
public class GenericExtends {
    public static void main(String[] args) {
        Object o = new String("xx");
        // 泛型没有继承性
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();
        
        // 1. List<?> c: 可以接受任意的泛型类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);
        
        // 2. List<? extends AA> c: 表示上限,可以接受AA 或者AA 子类
        // printCollection2(list1);//×
        // printCollection2(list2);//×
        printCollection2(list3);//√
        printCollection2(list4);//√
        printCollection2(list5);//√
        
        // 3. List<? super AA> c: 支持AA 类以及AA 类的父类,不限于直接父类
        printCollection3(list1);//√
        // printCollection3(list2);//×
        printCollection3(list3);//√
        // printCollection3(list4);//×
        // printCollection3(list5);//×
    }

    // 说明: List<?> 表示任意的泛型类型都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }

    // ? extends AA 表示上限,可以接受AA 或者AA 子类
    public static void printCollection2(List<? extends AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }

    // ? super 子类类名AA:支持AA 类以及AA 类的父类,不限于直接父类,规定了泛型的下限
    public static void printCollection3(List<? super AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
}

class AA {}
class BB extends AA {}
class CC extends BB {}

本章作业

编程题 Homework01.java(10min)

  1. 定义泛型类 DAO<T>,包含 Map 成员变量(键为 String 类型,值为 T 类型)
  2. 实现以下方法:
    • public void save(String id, T entity): 保存 T 类型对象到 Map
    • public T get(String id): 从 Map 中获取 id 对应的对象
    • public void update(String id, T entity): 替换 Mapkeyid 的内容
    • public List<T> list(): 返回 Map 中所有 T 对象
    • public void delete(String id): 删除指定 id 的对象
  3. 定义 User 类:包含私有成员变量 id(int)、age(int)、name(String)
  4. 创建 DAO 类对象,调用上述方法操作 User 对象,使用 JUnit 单元测试

JUnit

为什么需要JUnit

  1. 传统方式需在 main 方法中编写测试代码
  2. 多个功能测试需来回注销,切换麻烦
  3. 需直接运行单个方法并获取测试信息

基本介绍

  1. JUnit 是 Java 语言的单元测试框架
  2. 多数 Java 开发环境已集成 JUnit

使用示例(JUnit 5)

java
package com.hspedu.junit_;

import org.junit.jupiter.api.Test;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class JUnit_ {
    // 传统方式测试
    public static void main(String[] args) {
        new JUnit_().m1();
        new JUnit_().m2();
    }

    @Test
    public void m1() {
        System.out.println("m1 方法被调用");
    }

    @Test
    public void m2() {
        System.out.println("m2 方法被调用");
    }

    @Test
    public void m3() {
        System.out.println("m3 方法被调用");
    }
}